/*
 * Copyright 2025 arisgo@163.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arisgo.cloud.auth.login.granter;

import com.arisgo.cloud.auth.login.model.Login;
import com.arisgo.cloud.auth.sys.service.TenantService;
import com.arisgo.cloud.auth.sys.vo.TenantVo;
import com.arisgo.cloud.common.utils.AssertUtil;
import com.arisgo.cloud.core.Constant;
import com.arisgo.cloud.core.redis.UserInfo;
import com.arisgo.cloud.core.token.TokenGenerator;
import com.arisgo.cloud.core.utils.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.wf.captcha.SpecCaptcha;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author Arisgo
 * @since 2024/6/1
 */
public abstract class AbstractLoginGranter implements ILoginGranter {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    protected final RedisTemplate<String, Object> redisTemplate;
    protected final TokenGenerator tokenGenerator;
    protected final TenantService tenantService;

    protected ObjectMapper mapper = new ObjectMapper();

    public AbstractLoginGranter(RedisTemplate<String, Object> redisTemplate,
                                TokenGenerator tokenGenerator,
                                TenantService tenantService) {
        this.redisTemplate = redisTemplate;
        this.tokenGenerator = tokenGenerator;
        this.tenantService = tenantService;
    }

    @Override
    public Map<Object, Object> login(Login login) {
        // 校验登陆信息
        UserInfo user = checkLogin(login);
        // 设置租户
        findTenant(user);
        // 生成token
        return tokenGenerator.generator(user);
    }

    protected void checkCaptcha(String captcha) {
        AssertUtil.hasLength(captcha, "验证码为空！");

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        AssertUtil.notNull(attributes, "请求异常！");
        String key = attributes.getRequest().getHeader(Constant.CAPTCHA_KEY);
        AssertUtil.hasLength(key, "请求异常！");

        String code = (String) redisTemplate.opsForValue().get(key);
        AssertUtil.isTrue(captcha.equalsIgnoreCase(code), "验证码错误！");
    }

    protected abstract UserInfo checkLogin(Login login);

    protected void findTenant(UserInfo user) {
        Long tenantId = user.getTenantId();
        // 校验租户，租户下的账户是否可用
        if (tenantService.checkTenantAccount(tenantId, user.getUserId())) {
            TenantVo tenant = tenantService.findById(tenantId);
            user.setTenantName(tenant.getName());
            user.setOwner(user.getUserId().equals(tenant.getUserId()));
            logger.info("The default tenant id is {}, tenant name is {}", tenant.getId(), tenant.getName());
        } else {
            user.setTenantId(null);
            user.setTenantName(null);
            user.setOwner(false);
            logger.warn("{}, The id of tenant is invalid !", tenantId);
        }

    }

    @Override
    public String refreshToken(String refreshToken) {
        // 验证
        AssertUtil.isTrue(JwtUtil.verify(refreshToken, tokenGenerator.getTokenSecret()), "refresh token exception!");
        String userId = JwtUtil.getClaim(refreshToken, "aud", String.class);
        String token = (String) redisTemplate.opsForHash().get(Constant.TOKEN_PREFIX + userId, Constant.REFRESH_TOKEN);
        AssertUtil.isTrue(refreshToken.equals(token), "invalid token!");
        String accessToken = (String) redisTemplate.opsForHash().get(Constant.TOKEN_PREFIX + userId, Constant.ACCESS_TOKEN);
        AssertUtil.notNull(accessToken, "invalid login!");
        UserInfo user = JwtUtil.getClaim(accessToken, "user", UserInfo.class);
        // 刷新
        return tokenGenerator.accessToken(user);
    }

    @Override
    public Boolean logout(Long userId) {
        return redisTemplate.delete(Constant.TOKEN_PREFIX + userId.toString());
    }

    @Override
    public Map<String, Object> captcha() {
        SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);
        String code = captcha.text().toLowerCase();
        String key = Constant.CAPTCHA_CACHE + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(key, code, 5L, TimeUnit.MINUTES);
        logger.info("captcha code: {}, captcha key: {}", code, key);

        // 将key和base64返回给前端
        Map<String, Object> data = Maps.newLinkedHashMap();
        data.put("key", key);
        data.put("image", captcha.toBase64());
        return data;
    }
}
